library(ggplot2)

Introduction

In this tutorial you will be introduced to r5r: an interface to the R5 routing engine developed by Conveyal. R5 (r5r) allows you to do “rapid, realistic” routing on multimodal networks. It’s very flexible, enabling intermodal analyses and has the key strength of retaining the full detail of the public transport timetable. If you’re more familiar with python, you can consider using r5py instead - although it may not have all of the same functionality.

As an example, we’re going to be analyzing accessibility by public transport to beer gardens 🚇🍻

The tutorial consists of the following main steps

  1. Installing r5r
  2. GTFS preparation
  3. Loading spatial data into R
  4. Creating an Isochrone

In addition to r5r, you will be exposed to several other useful R packages: dplyr (for working with data tables), sf (for working with geodata), and tidytransit (for working with GTFS data). These are all very powerful packages that can make your life a lot easier if you learn how to use them! You’re encouraged to look into the documentation of these packages for more information.

Installing r5r

All the packages listed below (including r5r) can be installed by clicking “packages > install” on the sidebar and searching for the package name or by executing the following command: install.packages("package_name_here")

Since R5 (the routing engine itself) is written in java, we need to install the Java Development Kit to make r5r work. Specifically we need version 21. You can install it here: https://www.oracle.com/de/java/technologies/downloads/#java21

With that done, we can load all the packages we’ll be using today.

library(tidyverse)
Warning: package ‘tidyverse’ was built under R version 4.1.3Warning: package ‘tibble’ was built under R version 4.1.3Warning: package ‘tidyr’ was built under R version 4.1.3Warning: package ‘forcats’ was built under R version 4.1.3-- Attaching core tidyverse packages ------------------------------------------------------------ tidyverse 2.0.0 --
v dplyr     1.1.4     v readr     2.1.4
v forcats   1.0.0     v stringr   1.5.1
v ggplot2   3.5.1     v tibble    3.2.1
v lubridate 1.9.4     v tidyr     1.3.0
v purrr     1.0.4     -- Conflicts ------------------------------------------------------------------------------ tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
i Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(sf)
Linking to GEOS 3.10.2, GDAL 3.4.1, PROJ 7.2.1; sf_use_s2() is TRUE
library(tidytransit)

options(java.parameters = '-Xmx4G') # we need to allocate RAM for r5r to work. You can increase this depending on your system resources.
library(r5r)
Please make sure you have already allocated some memory to Java by running:
  options(java.parameters = '-Xmx2G').
You should replace '2G' by the amount of memory you'll require. Currently, Java memory is set to -Xmx4G

GTFS Preparation

  1. Download data from: https://gtfs.de/ - pick “Deutschland gesamt”
  2. Move the zip file into the “raw_data” folder
  3. Using the full feed for all of Germany in r5r will require a lot of resources. Since we’ll be analyzing a single city - lets filter the feed so that it is smaller. We’ll be using tidytransit for this.
  4. Let’s first load in geodata representing the area we’ll be trimming to (in this case - Munich). We’ll use st_read from the sf package for this. It can handle all typical geospatial file types (.shp,.gpkg,etc.)
area <- st_read("./raw_data/munich_admin.gpkg")
Reading layer `boundary_administrative_munich' from data source 
  `C:\Users\barte\Documents\R\ilutm-r5r-tutorial\raw_data\munich_admin.gpkg' using driver `GPKG'
Simple feature collection with 25 features and 2 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 11.36078 ymin: 48.06162 xmax: 11.72291 ymax: 48.24812
Geodetic CRS:  WGS 84
  1. It’s good practice to buffer our study area to avoid any edge effects. Let’s buffer our area by 5 km. NOTE: in this analysis we’re just going to be looking at accessibility to beer gardens in Munich. We aren’t considering any that are in the municipalities around the city. If your destinations extend beyond the border of your city, you may need to buffer your study area significantly! (>>5 km)
  2. We’ve seen how to do this in QGIS, let’s use sf this time
area_buffered <- st_transform(area,crs = 25832) # switch crs to projected (ETRS89 / UTM zone 32N)

area_buffered <- st_union(area_buffered) # join geometries 

area_buffered <- st_buffer(area_buffered, 5000) # buffer by 5km

area_buffered <- st_transform(area_buffered,crs = 4326) #switch back to WGS84 for filtering the GTFS feed


ggplot()+
  geom_sf(data = area_buffered)+
  geom_sf(data = area)

7.Let’s load in the GTFS feed we downloaded and trim it. We can see that the number of stops in the feed has been significantly reduced.

  1. Let’s save the filtered feed. R5 will use this to build a network, you can also use it for other public transport analyses (refer to previous tutorials). Tip: tidytransit has a lot of great functionality for this!

write_gtfs(gtfs_filtered,"./r5r_model/gtfs.zip")

r5r Set Up

  1. Before running any more code we need to supply the correct data. So far we have prepared the GTFS data, but we also need a street network. R5 uses an extract of OSM data to build the street network. To do this, it needs to be provided with a .pbf file. This can be found on Geofabrik or https://slice.openstreetmap.us/. OSM by the slice is recommended because it has a cool name and because you can select the exact area you’re interested in (reducing network size, improving performance).
  2. Just as when we were trimming the GTFS feed, extract an area larger than the city itself.

  1. Save the .pbf file to the /r5r_model/ folder. The folder should look like this:

  2. We can now tell r5 to build the network. Tip: if you are sure you aren’t modifying the GTFS or .pbf data, you can turn overwrite = F to avoid rebuilding the model (faster).

r5r_core <- setup_r5(data_path = "./r5r_model", verbose = FALSE,overwrite = T)
No raster .tif files found. Using elevation = 'NONE'.

Finished building network.dat at ./r5r_model/network.dat

OD Data

We need some data to use for origins and destinations. Our destinations will be beer gardens. The data is provided for you. It was prepared using an Overpass API query (as in previous tutorials).

beergardens <- st_read("raw_data/biergarten.gpkg")
Reading layer `centroids' from data source `C:\Users\barte\Documents\R\ilutm-r5r-tutorial\raw_data\biergarten.gpkg' using driver `GPKG'
Simple feature collection with 100 features and 3 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: 11.3911 ymin: 48.07886 xmax: 11.71046 ymax: 48.21359
Geodetic CRS:  WGS 84
ggplot()+
  geom_sf(data = area)+
  geom_sf(data = beergardens)

For our origins, we’ll use public transport stops. As we discussed in lecture, a single “stop” may be represented by multiple in a GTFS feed (e.g., a bus stop on both sides of the street). We can take some steps to simplify this. We will group all stops that share the same value for parent_station and then keep a single coordinate by taking the average. Be aware that solving this in a line of code is often too good to be true. GTFS data often contains mistakes, and may require more careful cleaning!

stops <- summarize(group_by(gtfs_filtered$stops, parent_station),
                   stop_lat = mean(stop_lat),
                   stop_lon = mean(stop_lon),
                   parent_station_name = first(stop_name))  
stops

As a final step lets convert the table to geodata and filter it down to our study area.

stops <- st_as_sf(stops,coords = c("stop_lon","stop_lat"),crs = 4326,remove = F)

stops <-st_filter(stops,area) #apply spatial filter

stops
Simple feature collection with 1125 features and 4 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: 11.38896 ymin: 48.06913 xmax: 11.71527 ymax: 48.22
Geodetic CRS:  WGS 84
ggplot()+
  geom_sf(data = area)+
  geom_sf(data = stops, color = "blue")+
  geom_sf(data = beergardens, size = 2)

beergardens
Simple feature collection with 100 features and 4 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: 11.3911 ymin: 48.07886 xmax: 11.71046 ymax: 48.21359
Geodetic CRS:  WGS 84
First 10 features:
       osm_id    amenity                    name                      geom         id
1   110892783 biergarten Lindengarten Biergarten POINT (11.67838 48.11507)  110892783
2   368977873 biergarten         Tivoli Pavillon POINT (11.60341 48.16053)  368977873
3   502190647 biergarten             Olympia-Alm POINT (11.55432 48.17114)  502190647
4   292910957 biergarten       Tambosi Hofgarten  POINT (11.5786 48.14293)  292910957
5  1371548072 biergarten            Zum Biereder POINT (11.54993 48.16336) 1371548072
6    72273510 biergarten                Hirschau POINT (11.60248 48.16209)   72273510
7   343096349 biergarten          Paulanergarten  POINT (11.5718 48.20859)  343096349
8   325104410 biergarten   Ristorante Dolce Vita POINT (11.52478 48.08895)  325104410
9  2907073039 biergarten         Zur Gartenlaube POINT (11.60551 48.10014) 2907073039
10 1329567304 biergarten           La Locandiera  POINT (11.6409 48.08744) 1329567304

–> Before we start using r5r, lets format our data. The origins and destinations should have an id column. stops already has this, but beergardens does not. Let’s add a new column called id, and set the values to equal osm_id.

beergardens <- mutate(beergardens, id = osm_id)
stops <- mutate(stops, id = parent_station)

beergardens
Simple feature collection with 100 features and 4 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: 11.3911 ymin: 48.07886 xmax: 11.71046 ymax: 48.21359
Geodetic CRS:  WGS 84
First 10 features:
       osm_id    amenity                    name                      geom         id
1   110892783 biergarten Lindengarten Biergarten POINT (11.67838 48.11507)  110892783
2   368977873 biergarten         Tivoli Pavillon POINT (11.60341 48.16053)  368977873
3   502190647 biergarten             Olympia-Alm POINT (11.55432 48.17114)  502190647
4   292910957 biergarten       Tambosi Hofgarten  POINT (11.5786 48.14293)  292910957
5  1371548072 biergarten            Zum Biereder POINT (11.54993 48.16336) 1371548072
6    72273510 biergarten                Hirschau POINT (11.60248 48.16209)   72273510
7   343096349 biergarten          Paulanergarten  POINT (11.5718 48.20859)  343096349
8   325104410 biergarten   Ristorante Dolce Vita POINT (11.52478 48.08895)  325104410
9  2907073039 biergarten         Zur Gartenlaube POINT (11.60551 48.10014) 2907073039
10 1329567304 biergarten           La Locandiera  POINT (11.6409 48.08744) 1329567304

TTM<-
travel_time_matrix(
  r5r_core,
  origins = stops, 
  destinations = stops, 
  mode = c("TRANSIT"),  
  mode_egress = "WALK", 
  departure_datetime = as.POSIXct("26-05-2025 9:00:00", format = "%d-%m-%Y %H:%M:%S"), #its important that the date and time is within your GTFS feed
  time_window = 10L, # 
  percentiles = c(10,50),
  max_walk_time = 10,
  max_trip_duration = 30L,
  walk_speed = 4,
  max_rides = 3,
)

TTM

Travel Time Matrix

The main functionality of r5r is the ability to calculate a travel time matrix. Knowing the travel time between pairs of points is useful on its own. It can also be used if you want more control over your accessibility calculations. For example, if you calculate a travel time matrix between pairs of public transport stops, you can calculate the access (origin –> first public transport stop) and egress (last public transport stop –> destination) legs of the trip independently. In other words, combining the output of r5r with the QGIS-based accessibility calculations we did in earlier tutorials.

Let’s give it a shot:

tip: there are many other settings you can change, you’re encouraged to read the documentation of r5r to see what is possible! The documentation has many nice examples!

  • stops: our origins are the public transport stops
  • destinations: our destinations are the public transport stops
  • mode: the primary mode is public transport (which includes walking for access and transfers)
  • mode_egress: the egress mode is walking
  • departure_datetime: the time trips start
  • time_window: set to 60. A travel time matrix will be calculated for each minute, for 60 minutes after the departure time. This allows for us to account for the variability in travel times. Imagine we ran our analysis only at 8:00 and we just missed a departure of an S-Bahn at 7:59, the travel time that we’d calculate would be severly penalized by excessive waiting. This parameter can be reduced (default is 10) as it can be computationally expensive. It’s increasingly important in suburban and rural areas where service frequencies are low.
  • percentiles: this corresponds to the time_window. a value of 50 will report the median travel time. We can specify multiple percentiles by using a vector c()
  • max_walk_time: max time for access, egress, and transfers by walking
  • walk_speed: (kph)
  • max_ride: maximum # of transfers
TTM <- mutate(TTM,tt_diff = travel_time_p50 - travel_time_p10)

print(paste0(round(sum(is.na(TTM$tt_diff)) / length(TTM$tt_diff)*100,0),"%"))
[1] "30%"

The impact of the time_frame can be quite significant! The percent of OD pairs that had a 50th percentile travel time > 30 min and a 10th percentile travel time <30 min:

TTM$tt_diff%>%na.omit()%>%summary()
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   2.000   3.000   3.127   4.000  21.000 
TTM<-TTM%>%select(-tt_diff)

Those that could be reached, were this much slower:

TTM
Simple feature collection with 294664 features and 5 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: 11.38896 ymin: 48.06913 xmax: 11.71527 ymax: 48.22
Geodetic CRS:  WGS 84
First 10 features:
   from_id  to_id travel_time_p10 travel_time_p50 parent_station_name                  geometry
1   100060 100060               0               0   Josef-Lang-Straße POINT (11.47202 48.15011)
2   100060  10157              29              NA   Josef-Lang-Straße POINT (11.46016 48.18474)
3   100060 105596              28              NA   Josef-Lang-Straße POINT (11.48913 48.18399)
4   100060 109574              23              27   Josef-Lang-Straße POINT (11.47472 48.12805)
5   100060  11520              25              27   Josef-Lang-Straße POINT (11.54544 48.14428)
6   100060 128167              22              26   Josef-Lang-Straße POINT (11.46642 48.19177)
7   100060 131549              21              25   Josef-Lang-Straße POINT (11.44335 48.16361)
8   100060 131638              21              25   Josef-Lang-Straße POINT (11.47278 48.17485)
9   100060 132365              19              23   Josef-Lang-Straße  POINT (11.4531 48.14563)
10  100060   1335              27              NA   Josef-Lang-Straße POINT (11.45716 48.20257)

Let’s get the travel time matrix ready for an export. We will restructure the data so that the geometry of the destination stops (to_id) is added to each row. We will also add the name of the origin station so that the data is easier to work with. This is a similar operation to a VLOOKUP in excel. We use the from_id and to_id fields as they represent the parent_station.

For more guidance on dplyr functions (like left_join()) check out this cheat sheet

This format is easier to interpret if we take a single stop. For example, lets find all stops that can be reached from Theresienstraße within a median travel time of 20 minutes.

st_write(TTM,"./output/TTM.gpkg",append = F)
Writing layer `TTM' to data source `./output/TTM.gpkg' using driver `GPKG'
Writing 294664 features with 5 fields and geometry type Point.

Let’s export this so that we can work with it in QGIS.

st_write(TTM,"./output/TTM.gpkg",append = F)

We can achieve the same result as above by doing the following:

Then, by using what you’ve learned in previous tutorials, you can create service areas:

LS0tDQp0aXRsZTogIlI1UiBUdXRvcmlhbCINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQotLS0NCg0KYGBge3J9DQpsaWJyYXJ5KGdncGxvdDIpDQpgYGANCg0KDQojIEludHJvZHVjdGlvbg0KDQpJbiB0aGlzIHR1dG9yaWFsIHlvdSB3aWxsIGJlIGludHJvZHVjZWQgdG8gW3I1cl0oaHR0cHM6Ly9pcGVhZ2l0LmdpdGh1Yi5pby9yNXIvKTogYW4gaW50ZXJmYWNlIHRvIHRoZSBbUjVdKGh0dHBzOi8vZ2l0aHViLmNvbS9jb252ZXlhbC9yNSkgcm91dGluZyBlbmdpbmUgZGV2ZWxvcGVkIGJ5IENvbnZleWFsLg0KUjUgKHI1cikgYWxsb3dzIHlvdSB0byBkbyAicmFwaWQsIHJlYWxpc3RpYyIgcm91dGluZyBvbiBtdWx0aW1vZGFsIG5ldHdvcmtzLiBJdCdzIHZlcnkgZmxleGlibGUsIGVuYWJsaW5nDQppbnRlcm1vZGFsIGFuYWx5c2VzIGFuZCBoYXMgdGhlIGtleSBzdHJlbmd0aCBvZiByZXRhaW5pbmcgdGhlIGZ1bGwgZGV0YWlsIG9mIHRoZSBwdWJsaWMgdHJhbnNwb3J0IHRpbWV0YWJsZS4NCklmIHlvdSdyZSBtb3JlIGZhbWlsaWFyIHdpdGggcHl0aG9uLCB5b3UgY2FuIGNvbnNpZGVyIHVzaW5nIFtyNXB5XShodHRwczovL2dpdGh1Yi5jb20vcjVweS9yNXB5KSBpbnN0ZWFkIC0gYWx0aG91Z2ggaXQgbWF5IG5vdCBoYXZlIGFsbCBvZiB0aGUgc2FtZSBmdW5jdGlvbmFsaXR5Lg0KDQpBcyBhbiBleGFtcGxlLCB3ZSdyZSBnb2luZyB0byBiZSBhbmFseXppbmcgYWNjZXNzaWJpbGl0eSBieSBwdWJsaWMgdHJhbnNwb3J0IHRvIGJlZXIgZ2FyZGVucyDwn5qH8J+Nuw0KDQoNClRoZSB0dXRvcmlhbCBjb25zaXN0cyBvZiB0aGUgZm9sbG93aW5nIG1haW4gc3RlcHMNCg0KMS4gSW5zdGFsbGluZyByNXINCjIuIEdURlMgcHJlcGFyYXRpb24NCjMuIExvYWRpbmcgc3BhdGlhbCBkYXRhIGludG8gUg0KNC4gQ3JlYXRpbmcgYW4gSXNvY2hyb25lDQoNCg0KSW4gYWRkaXRpb24gdG8gcjVyLCB5b3Ugd2lsbCBiZSBleHBvc2VkIHRvIHNldmVyYWwgb3RoZXIgdXNlZnVsIFIgcGFja2FnZXM6IFtkcGx5cl0oaHR0cHM6Ly9kcGx5ci50aWR5dmVyc2Uub3JnLykgKGZvciB3b3JraW5nIHdpdGggZGF0YSB0YWJsZXMpLCBbc2ZdKGh0dHBzOi8vci1zcGF0aWFsLmdpdGh1Yi5pby9zZi8pIChmb3Igd29ya2luZyB3aXRoIGdlb2RhdGEpLCBhbmQgW3RpZHl0cmFuc2l0XShodHRwczovL2dpdGh1Yi5jb20vci10cmFuc2l0L3RpZHl0cmFuc2l0KSAoZm9yIHdvcmtpbmcgd2l0aCBHVEZTIGRhdGEpLiBUaGVzZSBhcmUgYWxsIHZlcnkNCnBvd2VyZnVsIHBhY2thZ2VzIHRoYXQgY2FuIG1ha2UgeW91ciBsaWZlIGEgbG90IGVhc2llciBpZiB5b3UgbGVhcm4gaG93IHRvIHVzZSB0aGVtISBZb3UncmUgZW5jb3VyYWdlZCANCnRvIGxvb2sgaW50byB0aGUgZG9jdW1lbnRhdGlvbiBvZiB0aGVzZSBwYWNrYWdlcyBmb3IgbW9yZSBpbmZvcm1hdGlvbi4NCg0KDQojIEluc3RhbGxpbmcgYHI1cmANCg0KQWxsIHRoZSBwYWNrYWdlcyBsaXN0ZWQgYmVsb3cgKGluY2x1ZGluZyBgcjVyYCkgY2FuIGJlIGluc3RhbGxlZCBieSBjbGlja2luZyAicGFja2FnZXMgPiBpbnN0YWxsIg0Kb24gdGhlIHNpZGViYXIgYW5kIHNlYXJjaGluZyBmb3IgdGhlIHBhY2thZ2UgbmFtZSBvciBieSBleGVjdXRpbmcgdGhlIGZvbGxvd2luZyBjb21tYW5kOiBgaW5zdGFsbC5wYWNrYWdlcygicGFja2FnZV9uYW1lX2hlcmUiKWANCg0KIVtdKC4vaW1nL2luc3RhbGxfcGFja2FnZXMucG5nKQ0KDQoNClNpbmNlIFI1ICh0aGUgcm91dGluZyBlbmdpbmUgaXRzZWxmKSBpcyB3cml0dGVuIGluIGphdmEsIHdlIG5lZWQgdG8gaW5zdGFsbCB0aGUgSmF2YSBEZXZlbG9wbWVudCBLaXQNCnRvIG1ha2UgYHI1cmAgd29yay4gU3BlY2lmaWNhbGx5IHdlIG5lZWQgdmVyc2lvbiAyMS4gWW91IGNhbiBpbnN0YWxsIGl0IGhlcmU6IGh0dHBzOi8vd3d3Lm9yYWNsZS5jb20vZGUvamF2YS90ZWNobm9sb2dpZXMvZG93bmxvYWRzLyNqYXZhMjENCg0KDQpXaXRoIHRoYXQgZG9uZSwgd2UgY2FuIGxvYWQgYWxsIHRoZSBwYWNrYWdlcyB3ZSdsbCBiZSB1c2luZyB0b2RheS4NCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeSh0aWR5dHJhbnNpdCkNCg0Kb3B0aW9ucyhqYXZhLnBhcmFtZXRlcnMgPSAnLVhteDRHJykgIyB3ZSBuZWVkIHRvIGFsbG9jYXRlIFJBTSBmb3IgcjVyIHRvIHdvcmsuIFlvdSBjYW4gaW5jcmVhc2UgdGhpcyBkZXBlbmRpbmcgb24geW91ciBzeXN0ZW0gcmVzb3VyY2VzLg0KbGlicmFyeShyNXIpDQpgYGANCg0KDQojIEdURlMgUHJlcGFyYXRpb24NCg0KMS4gRG93bmxvYWQgZGF0YSBmcm9tOiBodHRwczovL2d0ZnMuZGUvIC0gcGljayAiRGV1dHNjaGxhbmQgZ2VzYW10Ig0KMi4gTW92ZSB0aGUgemlwIGZpbGUgaW50byB0aGUgInJhd19kYXRhIiBmb2xkZXINCjMuIFVzaW5nIHRoZSBmdWxsIGZlZWQgZm9yIGFsbCBvZiBHZXJtYW55IGluIHI1ciB3aWxsIHJlcXVpcmUgYSBsb3Qgb2YgcmVzb3VyY2VzLiBTaW5jZSB3ZSdsbCBiZSANCmFuYWx5emluZyBhIHNpbmdsZSBjaXR5IC0gbGV0cyBmaWx0ZXIgdGhlIGZlZWQgc28gdGhhdCBpdCBpcyBzbWFsbGVyLiBXZSdsbCBiZSB1c2luZyBgdGlkeXRyYW5zaXRgIGZvciB0aGlzLg0KNC4gTGV0J3MgZmlyc3QgbG9hZCBpbiBnZW9kYXRhIHJlcHJlc2VudGluZyB0aGUgYXJlYSB3ZSdsbCBiZSB0cmltbWluZyB0byAoaW4gdGhpcyBjYXNlIC0gTXVuaWNoKS4gV2UnbGwgdXNlIGBzdF9yZWFkYCBmcm9tIHRoZSBgc2ZgIHBhY2thZ2UgZm9yIHRoaXMuIEl0IGNhbiBoYW5kbGUgYWxsIHR5cGljYWwgZ2Vvc3BhdGlhbCBmaWxlIHR5cGVzIChgLnNocGAsYC5ncGtnYCxldGMuKQ0KDQpgYGB7cn0NCmFyZWEgPC0gc3RfcmVhZCgiLi9yYXdfZGF0YS9tdW5pY2hfYWRtaW4uZ3BrZyIpDQoNCmdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGEgID0gYXJlYSkNCg0KYGBgDQo1LiBJdCdzIGdvb2QgcHJhY3RpY2UgdG8gYnVmZmVyIG91ciBzdHVkeSBhcmVhIHRvIGF2b2lkIGFueSBlZGdlIGVmZmVjdHMuIExldCdzIGJ1ZmZlciBvdXIgYXJlYSBieSA1IGttLiBOT1RFOiBpbiB0aGlzIGFuYWx5c2lzIHdlJ3JlIGp1c3QgZ29pbmcgdG8gYmUgbG9va2luZyBhdCBhY2Nlc3NpYmlsaXR5IHRvIGJlZXIgZ2FyZGVucyBpbiBNdW5pY2guIFdlIGFyZW4ndCBjb25zaWRlcmluZyBhbnkgdGhhdCBhcmUgaW4gdGhlIG11bmljaXBhbGl0aWVzIGFyb3VuZCB0aGUgY2l0eS4gSWYgeW91ciBkZXN0aW5hdGlvbnMgZXh0ZW5kIGJleW9uZCB0aGUgYm9yZGVyIG9mIHlvdXIgY2l0eSwgeW91IG1heSBuZWVkIHRvIGJ1ZmZlciB5b3VyIHN0dWR5IGFyZWEgc2lnbmlmaWNhbnRseSEgKD4+NSBrbSkNCjYuIFdlJ3ZlIHNlZW4gaG93IHRvIGRvIHRoaXMgaW4gUUdJUywgbGV0J3MgdXNlIGBzZmAgdGhpcyB0aW1lDQoNCmBgYHtyfQ0KYXJlYV9idWZmZXJlZCA8LSBzdF90cmFuc2Zvcm0oYXJlYSxjcnMgPSAyNTgzMikgIyBzd2l0Y2ggY3JzIHRvIHByb2plY3RlZCAoRVRSUzg5IC8gVVRNIHpvbmUgMzJOKQ0KDQphcmVhX2J1ZmZlcmVkIDwtIHN0X3VuaW9uKGFyZWFfYnVmZmVyZWQpICMgam9pbiBnZW9tZXRyaWVzIA0KDQphcmVhX2J1ZmZlcmVkIDwtIHN0X2J1ZmZlcihhcmVhX2J1ZmZlcmVkLCA1MDAwKSAjIGJ1ZmZlciBieSA1a20NCg0KYXJlYV9idWZmZXJlZCA8LSBzdF90cmFuc2Zvcm0oYXJlYV9idWZmZXJlZCxjcnMgPSA0MzI2KSAjc3dpdGNoIGJhY2sgdG8gV0dTODQgZm9yIGZpbHRlcmluZyB0aGUgR1RGUyBmZWVkDQoNCg0KZ2dwbG90KCkrDQogIGdlb21fc2YoZGF0YSA9IGFyZWFfYnVmZmVyZWQpKw0KICBnZW9tX3NmKGRhdGEgPSBhcmVhKQ0KYGBgDQo3LkxldCdzIGxvYWQgaW4gdGhlIEdURlMgZmVlZCB3ZSBkb3dubG9hZGVkIGFuZCB0cmltIGl0LiBXZSBjYW4gc2VlIHRoYXQgdGhlIG51bWJlciBvZiBzdG9wcyBpbiB0aGUgZmVlZCBoYXMgYmVlbiBzaWduaWZpY2FudGx5IHJlZHVjZWQuDQpgYGB7cn0NCg0KZ3Rmc19yYXcgPC0gcmVhZF9ndGZzKCIuL3Jhd19kYXRhL2xhdGVzdC56aXAiLGVuY29kaW5nID0gIlVURi04IikgIyBwYXRoIHNob3VsZCBwb2ludCB0byB0aGUgR1RGUyBmZWVkIHlvdSBkb3dubG9hZGVkDQoNCmd0ZnNfZmlsdGVyZWQgPC0gZmlsdGVyX2ZlZWRfYnlfYXJlYShndGZzX3JhdyxzdF9iYm94KGFyZWFfYnVmZmVyZWQpKQ0KDQoNCg0KdHJpYmJsZSh+bl9zdG9wc19yYXcsIH5uX3N0b3BzX2ZpbHRlcmVkLA0KICAgICAgICBucm93KGd0ZnNfcmF3JHN0b3BzKSwgbnJvdyhndGZzX2ZpbHRlcmVkJHN0b3BzKSkNCmBgYA0KDQoNCjguIExldCdzIHNhdmUgdGhlIGZpbHRlcmVkIGZlZWQuIFI1IHdpbGwgdXNlIHRoaXMgdG8gYnVpbGQgYSBuZXR3b3JrLCB5b3UgY2FuIGFsc28gdXNlIGl0IGZvciBvdGhlciBwdWJsaWMgdHJhbnNwb3J0IGFuYWx5c2VzIA0KKHJlZmVyIHRvIHByZXZpb3VzIHR1dG9yaWFscykuIFRpcDogYHRpZHl0cmFuc2l0YCBoYXMgYSBsb3Qgb2YgZ3JlYXQgZnVuY3Rpb25hbGl0eSBmb3IgdGhpcyENCmBgYHtyfQ0KDQp3cml0ZV9ndGZzKGd0ZnNfZmlsdGVyZWQsIi4vcjVyX21vZGVsL2d0ZnMuemlwIikNCg0KYGBgDQoNCg0KIyBgcjVyYCBTZXQgVXANCg0KMS4gQmVmb3JlIHJ1bm5pbmcgYW55IG1vcmUgY29kZSB3ZSBuZWVkIHRvIHN1cHBseSB0aGUgY29ycmVjdCBkYXRhLiBTbyBmYXIgd2UgaGF2ZSBwcmVwYXJlZCB0aGUgR1RGUyBkYXRhLCBidXQgd2UgYWxzbyBuZWVkIGEgc3RyZWV0IG5ldHdvcmsuIFI1IHVzZXMgYW4gZXh0cmFjdCBvZiBPU00gZGF0YSB0byBidWlsZCB0aGUgc3RyZWV0IG5ldHdvcmsuIFRvIGRvIHRoaXMsIGl0IG5lZWRzIHRvIGJlIHByb3ZpZGVkIHdpdGggYSBgLnBiZmAgZmlsZS4gVGhpcyBjYW4gYmUgZm91bmQgb24gW0dlb2ZhYnJpa10oaHR0cDovL2Rvd25sb2FkLmdlb2ZhYnJpay5kZS8pIG9yIGh0dHBzOi8vc2xpY2Uub3BlbnN0cmVldG1hcC51cy8uIE9TTSBieSB0aGUgc2xpY2UgaXMgcmVjb21tZW5kZWQgYmVjYXVzZSBpdCBoYXMgYSBjb29sIG5hbWUgYW5kIGJlY2F1c2UgeW91IGNhbiBzZWxlY3QgdGhlIGV4YWN0IGFyZWEgeW91J3JlIGludGVyZXN0ZWQgaW4gKHJlZHVjaW5nIG5ldHdvcmsgc2l6ZSwgaW1wcm92aW5nIHBlcmZvcm1hbmNlKS4NCjIuIEp1c3QgYXMgd2hlbiB3ZSB3ZXJlIHRyaW1taW5nIHRoZSBHVEZTIGZlZWQsIGV4dHJhY3QgYW4gYXJlYSBsYXJnZXIgdGhhbiB0aGUgY2l0eSBpdHNlbGYuDQoNCiFbXSguL2ltZy9zbGljZS5wbmcpDQoNCjMuIFNhdmUgdGhlIGAucGJmYCBmaWxlIHRvIHRoZSBgL3I1cl9tb2RlbC9gIGZvbGRlci4gVGhlIGZvbGRlciBzaG91bGQgbG9vayBsaWtlIHRoaXM6DQohW10oLi9pbWcvcjVyX2ZvbGRlci5wbmcpDQoNCjQuIFdlIGNhbiBub3cgdGVsbCByNSB0byBidWlsZCB0aGUgbmV0d29yay4gVGlwOiBpZiB5b3UgYXJlIHN1cmUgeW91IGFyZW4ndCBtb2RpZnlpbmcgdGhlIEdURlMgb3IgYC5wYmZgIGRhdGEsIHlvdSBjYW4gdHVybiBgb3ZlcndyaXRlID0gRmAgdG8gYXZvaWQgcmVidWlsZGluZyB0aGUgbW9kZWwgKGZhc3RlcikuDQoNCmBgYHtyfQ0KcjVyX2NvcmUgPC0gc2V0dXBfcjUoZGF0YV9wYXRoID0gIi4vcjVyX21vZGVsIiwgdmVyYm9zZSA9IEZBTFNFLG92ZXJ3cml0ZSA9IFQpDQpgYGANCiMgT0QgRGF0YQ0KDQpXZSBuZWVkIHNvbWUgZGF0YSB0byB1c2UgZm9yIG9yaWdpbnMgYW5kIGRlc3RpbmF0aW9ucy4gT3VyIGRlc3RpbmF0aW9ucyB3aWxsIGJlIGJlZXIgZ2FyZGVucy4gVGhlIGRhdGEgaXMgcHJvdmlkZWQgZm9yIHlvdS4gSXQgd2FzIHByZXBhcmVkIHVzaW5nIGFuIE92ZXJwYXNzIEFQSSBxdWVyeSAoYXMgaW4gcHJldmlvdXMgdHV0b3JpYWxzKS4NCg0KDQpgYGB7cn0NCmJlZXJnYXJkZW5zIDwtIHN0X3JlYWQoInJhd19kYXRhL2JpZXJnYXJ0ZW4uZ3BrZyIpDQoNCmdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGEgPSBhcmVhKSsNCiAgZ2VvbV9zZihkYXRhID0gYmVlcmdhcmRlbnMpDQpgYGANCg0KRm9yIG91ciBvcmlnaW5zLCB3ZSdsbCB1c2UgcHVibGljIHRyYW5zcG9ydCBzdG9wcy4gQXMgd2UgZGlzY3Vzc2VkIGluIGxlY3R1cmUsIGEgc2luZ2xlICJzdG9wIiBtYXkgYmUgcmVwcmVzZW50ZWQgYnkgbXVsdGlwbGUgaW4gYSBHVEZTIGZlZWQgKGUuZy4sIGEgYnVzIHN0b3Agb24gYm90aCBzaWRlcyBvZiB0aGUgc3RyZWV0KS4gV2UgY2FuIHRha2Ugc29tZSBzdGVwcyB0byBzaW1wbGlmeSB0aGlzLiBXZSB3aWxsIGdyb3VwIGFsbCBzdG9wcyB0aGF0IHNoYXJlIHRoZSBzYW1lIHZhbHVlIGZvciBgcGFyZW50X3N0YXRpb25gIGFuZCB0aGVuIGtlZXAgYSBzaW5nbGUgY29vcmRpbmF0ZSBieSB0YWtpbmcgdGhlIGF2ZXJhZ2UuIEJlIGF3YXJlIHRoYXQgc29sdmluZyB0aGlzIGluIGEgbGluZSBvZiBjb2RlIGlzIG9mdGVuIHRvbyBnb29kIHRvIGJlIHRydWUuIEdURlMgZGF0YSBvZnRlbiBjb250YWlucyBtaXN0YWtlcywgYW5kIG1heSByZXF1aXJlIG1vcmUgY2FyZWZ1bCBjbGVhbmluZyENCg0KYGBge3J9DQpzdG9wcyA8LSBzdW1tYXJpemUoZ3JvdXBfYnkoZ3Rmc19maWx0ZXJlZCRzdG9wcywgcGFyZW50X3N0YXRpb24pLA0KICAgICAgICAgICAgICAgICAgIHN0b3BfbGF0ID0gbWVhbihzdG9wX2xhdCksDQogICAgICAgICAgICAgICAgICAgc3RvcF9sb24gPSBtZWFuKHN0b3BfbG9uKSwNCiAgICAgICAgICAgICAgICAgICBwYXJlbnRfc3RhdGlvbl9uYW1lID0gZmlyc3Qoc3RvcF9uYW1lKSkgIA0Kc3RvcHMNCmBgYA0KDQpBcyBhIGZpbmFsIHN0ZXAgbGV0cyBjb252ZXJ0IHRoZSB0YWJsZSB0byBnZW9kYXRhIGFuZCBmaWx0ZXIgaXQgZG93biB0byBvdXIgc3R1ZHkgYXJlYS4NCg0KYGBge3J9DQpzdG9wcyA8LSBzdF9hc19zZihzdG9wcyxjb29yZHMgPSBjKCJzdG9wX2xvbiIsInN0b3BfbGF0IiksY3JzID0gNDMyNixyZW1vdmUgPSBGKQ0KDQpzdG9wcyA8LXN0X2ZpbHRlcihzdG9wcyxhcmVhKSAjYXBwbHkgc3BhdGlhbCBmaWx0ZXINCg0Kc3RvcHMNCmBgYA0KDQpgYGB7cn0NCmdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGEgPSBhcmVhKSsNCiAgZ2VvbV9zZihkYXRhID0gc3RvcHMsIGNvbG9yID0gImJsdWUiKSsNCiAgZ2VvbV9zZihkYXRhID0gYmVlcmdhcmRlbnMsIHNpemUgPSAyKQ0KYGBgDQo8IS0tDQpBcyB3ZSBjYW4gc2VlLCB0aGlzIGlzIGEgbG90IG9mIHB1YmxpYyB0cmFuc3BvcnQgc3RvcHMhIFdlIGNhbiBjb250aW51ZSBjbGVhbmluZyB0aGlzIHVwIC0gYnV0IGZvciB0aGUgcHVycG9zZSBvZiB0aGUgZXhlcmNpc2UsIGxldCdzIHVzZSBkYXRhIHRoYXQgd2UndmUgdXNlZCBpbiBhIHByZXZpb3VzIHR1dG9yaWFsIChyYWlsIHN0b3BzKS4gVGhlIGJlZXIgZ2FyZGVuIGRhdGEgd2FzIHByb3ZpZGVkIGFzIGEgYC5ncGtnYCB3aGljaCB3ZSB3ZXJlIGFibGUgdG8gcmVhZCB1c2luZyBgc3RfcmVhZCgpYC4gQXMgdGhpcyBkYXRhIGlzIGEgYC5jc3ZgIHdlIHdpbGwgdXNlIGByZWFkX2NzdigpYCBhbmQgdGhlbiBjb252ZXJ0IGl0IGludG8gYSBnZW9kYXRhIGZvcm1hdC4NCg0KYGBge3J9DQpzdG9wcyA8LSByZWFkX2NzdigiLi9yYXdfZGF0YS9NdW5pY2hfU3RhdGlvbnMuY3N2IikNCg0Kc3RvcHMgPC0gc3RfYXNfc2Yoc3RvcHMsY29vcmRzID0gYygibG9uIiwibGF0IiksY3JzID0gNDMyNixyZW1vdmUgPSBGKQ0KDQpnZ3Bsb3QoKSsNCiAgZ2VvbV9zZihkYXRhID0gYXJlYSkrDQogIGdlb21fc2YoZGF0YSA9IHN0b3BzLCBjb2xvciA9ICJibHVlIikrDQogIGdlb21fc2YoZGF0YSA9IGJlZXJnYXJkZW5zLCBzaXplID0gMikNCmBgYA0KDQotLT4NCkJlZm9yZSB3ZSBzdGFydCB1c2luZyByNXIsIGxldHMgZm9ybWF0IG91ciBkYXRhLiBUaGUgb3JpZ2lucyBhbmQgZGVzdGluYXRpb25zIHNob3VsZCBoYXZlIGFuIGBpZGAgY29sdW1uLiBgc3RvcHNgIGFscmVhZHkgaGFzIHRoaXMsIGJ1dCBgYmVlcmdhcmRlbnNgIGRvZXMgbm90LiBMZXQncyBhZGQgYSBuZXcgY29sdW1uIGNhbGxlZCBgaWRgLCBhbmQgc2V0IHRoZSB2YWx1ZXMgdG8gZXF1YWwgYG9zbV9pZGAuDQoNCmBgYHtyfQ0KYmVlcmdhcmRlbnMNCmBgYA0KDQoNCmBgYHtyfQ0KYmVlcmdhcmRlbnMgPC0gbXV0YXRlKGJlZXJnYXJkZW5zLCBpZCA9IG9zbV9pZCkNCnN0b3BzIDwtIG11dGF0ZShzdG9wcywgaWQgPSBwYXJlbnRfc3RhdGlvbikNCg0KYmVlcmdhcmRlbnMNCmBgYA0KDQoNCiMgVHJhdmVsIFRpbWUgTWF0cml4DQoNClRoZSBtYWluIGZ1bmN0aW9uYWxpdHkgb2YgcjVyIGlzIHRoZSBhYmlsaXR5IHRvIGNhbGN1bGF0ZSBhIHRyYXZlbCB0aW1lIG1hdHJpeC4gS25vd2luZyB0aGUgdHJhdmVsIHRpbWUgYmV0d2VlbiBwYWlycyBvZiBwb2ludHMgaXMgdXNlZnVsIG9uIGl0cyBvd24uIEl0IGNhbiBhbHNvIGJlIHVzZWQgaWYgeW91IHdhbnQgbW9yZSBjb250cm9sIG92ZXIgeW91ciBhY2Nlc3NpYmlsaXR5IGNhbGN1bGF0aW9ucy4gRm9yIGV4YW1wbGUsIGlmIHlvdSBjYWxjdWxhdGUgYSB0cmF2ZWwgdGltZSBtYXRyaXggYmV0d2VlbiBwYWlycyBvZiBwdWJsaWMgdHJhbnNwb3J0IHN0b3BzLCB5b3UgY2FuIGNhbGN1bGF0ZSB0aGUgYWNjZXNzIChvcmlnaW4gLS0+IGZpcnN0IHB1YmxpYyB0cmFuc3BvcnQgc3RvcCkgYW5kIGVncmVzcyAobGFzdCBwdWJsaWMgdHJhbnNwb3J0IHN0b3AgLS0+IGRlc3RpbmF0aW9uKSBsZWdzIG9mIHRoZSB0cmlwIGluZGVwZW5kZW50bHkuIEluIG90aGVyIHdvcmRzLCBjb21iaW5pbmcgdGhlIG91dHB1dCBvZiByNXIgd2l0aCB0aGUgUUdJUy1iYXNlZCBhY2Nlc3NpYmlsaXR5IGNhbGN1bGF0aW9ucyB3ZSBkaWQgaW4gZWFybGllciB0dXRvcmlhbHMuDQoNCkxldCdzIGdpdmUgaXQgYSBzaG90Og0KDQp0aXA6IHRoZXJlIGFyZSBtYW55IG90aGVyIHNldHRpbmdzIHlvdSBjYW4gY2hhbmdlLCB5b3UncmUgZW5jb3VyYWdlZCB0byByZWFkIHRoZSBkb2N1bWVudGF0aW9uIG9mIHI1ciB0byBzZWUgd2hhdCBpcyBwb3NzaWJsZSEgVGhlIFtkb2N1bWVudGF0aW9uXShodHRwczovL2lwZWFnaXQuZ2l0aHViLmlvL3I1ci8pIGhhcyBtYW55IG5pY2UgZXhhbXBsZXMhDQoNCi0gYHN0b3BzYDogb3VyIG9yaWdpbnMgYXJlIHRoZSBwdWJsaWMgdHJhbnNwb3J0IHN0b3BzDQotIGBkZXN0aW5hdGlvbnNgOiBvdXIgZGVzdGluYXRpb25zIGFyZSB0aGUgcHVibGljIHRyYW5zcG9ydCBzdG9wcw0KLSBgbW9kZWA6IHRoZSBwcmltYXJ5IG1vZGUgaXMgcHVibGljIHRyYW5zcG9ydCAod2hpY2ggaW5jbHVkZXMgd2Fsa2luZyBmb3IgYWNjZXNzIGFuZCB0cmFuc2ZlcnMpDQotIGBtb2RlX2VncmVzc2A6IHRoZSBlZ3Jlc3MgbW9kZSBpcyB3YWxraW5nDQotIGBkZXBhcnR1cmVfZGF0ZXRpbWVgOiB0aGUgdGltZSB0cmlwcyBzdGFydA0KLSBgdGltZV93aW5kb3dgOiBzZXQgdG8gNjAuIEEgdHJhdmVsIHRpbWUgbWF0cml4IHdpbGwgYmUgY2FsY3VsYXRlZCBmb3IgZWFjaCBtaW51dGUsIGZvciA2MCBtaW51dGVzIGFmdGVyIHRoZSBkZXBhcnR1cmUgdGltZS4gVGhpcyBhbGxvd3MgZm9yIHVzIHRvIGFjY291bnQgZm9yIHRoZSB2YXJpYWJpbGl0eSBpbiB0cmF2ZWwgdGltZXMuIEltYWdpbmUgd2UgcmFuIG91ciBhbmFseXNpcyBvbmx5IGF0IDg6MDAgYW5kIHdlIGp1c3QgbWlzc2VkIGEgZGVwYXJ0dXJlIG9mIGFuIFMtQmFobiBhdCA3OjU5LCB0aGUgdHJhdmVsIHRpbWUgdGhhdCB3ZSdkIGNhbGN1bGF0ZSB3b3VsZCBiZSBzZXZlcmx5IHBlbmFsaXplZCBieSBleGNlc3NpdmUgd2FpdGluZy4gVGhpcyBwYXJhbWV0ZXIgY2FuIGJlIHJlZHVjZWQgKGRlZmF1bHQgaXMgMTApIGFzIGl0IGNhbiBiZSBjb21wdXRhdGlvbmFsbHkgZXhwZW5zaXZlLiBJdCdzIGluY3JlYXNpbmdseSBpbXBvcnRhbnQgaW4gc3VidXJiYW4gYW5kIHJ1cmFsIGFyZWFzIHdoZXJlIHNlcnZpY2UgZnJlcXVlbmNpZXMgYXJlIGxvdy4NCi0gYHBlcmNlbnRpbGVzYDogdGhpcyBjb3JyZXNwb25kcyB0byB0aGUgYHRpbWVfd2luZG93YC4gYSB2YWx1ZSBvZiA1MCB3aWxsIHJlcG9ydCB0aGUgbWVkaWFuIHRyYXZlbCB0aW1lLiBXZSBjYW4gc3BlY2lmeSBtdWx0aXBsZSBwZXJjZW50aWxlcyBieSB1c2luZyBhIHZlY3RvciBgYygpYA0KLSBgbWF4X3dhbGtfdGltZWA6IG1heCB0aW1lIGZvciBhY2Nlc3MsIGVncmVzcywgYW5kIHRyYW5zZmVycyBieSB3YWxraW5nDQotIGB3YWxrX3NwZWVkYDogKGtwaCkNCi0gYG1heF9yaWRlYDogbWF4aW11bSAjIG9mIHRyYW5zZmVycw0KDQoNCg0KYGBge3J9DQoNClRUTTwtDQp0cmF2ZWxfdGltZV9tYXRyaXgoDQogIHI1cl9jb3JlLA0KICBvcmlnaW5zID0gc3RvcHMsIA0KICBkZXN0aW5hdGlvbnMgPSBzdG9wcywgDQogIG1vZGUgPSBjKCJUUkFOU0lUIiksICANCiAgbW9kZV9lZ3Jlc3MgPSAiV0FMSyIsIA0KICBkZXBhcnR1cmVfZGF0ZXRpbWUgPSBhcy5QT1NJWGN0KCIyNi0wNS0yMDI1IDk6MDA6MDAiLCBmb3JtYXQgPSAiJWQtJW0tJVkgJUg6JU06JVMiKSwgI2l0cyBpbXBvcnRhbnQgdGhhdCB0aGUgZGF0ZSBhbmQgdGltZSBpcyB3aXRoaW4geW91ciBHVEZTIGZlZWQNCiAgdGltZV93aW5kb3cgPSAxMEwsICMgDQogIHBlcmNlbnRpbGVzID0gYygxMCw1MCksDQogIG1heF93YWxrX3RpbWUgPSAxMCwNCiAgbWF4X3RyaXBfZHVyYXRpb24gPSAzMEwsDQogIHdhbGtfc3BlZWQgPSA0LA0KICBtYXhfcmlkZXMgPSAzLA0KKQ0KDQpUVE0NCmBgYA0KDQpUaGUgaW1wYWN0IG9mIHRoZSB0aW1lX2ZyYW1lIGNhbiBiZSBxdWl0ZSBzaWduaWZpY2FudCEgVGhlIHBlcmNlbnQgb2YgT0QgcGFpcnMgdGhhdCBoYWQgYSA1MHRoIHBlcmNlbnRpbGUgdHJhdmVsIHRpbWUgPiAzMCBtaW4gYW5kIGEgMTB0aCBwZXJjZW50aWxlIHRyYXZlbCB0aW1lIDwzMCBtaW46DQoNCmBgYHtyfQ0KVFRNIDwtIG11dGF0ZShUVE0sdHRfZGlmZiA9IHRyYXZlbF90aW1lX3A1MCAtIHRyYXZlbF90aW1lX3AxMCkNCg0KcHJpbnQocGFzdGUwKHJvdW5kKHN1bShpcy5uYShUVE0kdHRfZGlmZikpIC8gbGVuZ3RoKFRUTSR0dF9kaWZmKSoxMDAsMCksIiUiKSkNCmBgYA0KDQpUaG9zZSB0aGF0IGNvdWxkIGJlIHJlYWNoZWQsIHdlcmUgdGhpcyBtdWNoIHNsb3dlcjoNCmBgYHtyfQ0KVFRNJHR0X2RpZmYlPiVuYS5vbWl0KCklPiVzdW1tYXJ5KCkNCg0KVFRNPC1UVE0lPiVzZWxlY3QoLXR0X2RpZmYpDQpgYGANCkxldCdzIGdldCB0aGUgdHJhdmVsIHRpbWUgbWF0cml4IHJlYWR5IGZvciBhbiBleHBvcnQuIFdlIHdpbGwgcmVzdHJ1Y3R1cmUgdGhlIGRhdGEgc28gdGhhdCB0aGUgZ2VvbWV0cnkgb2YgdGhlIGRlc3RpbmF0aW9uIHN0b3BzIChgdG9faWRgKSBpcyBhZGRlZCB0byBlYWNoIHJvdy4gV2Ugd2lsbCBhbHNvIGFkZCB0aGUgbmFtZSBvZiB0aGUgb3JpZ2luIHN0YXRpb24gc28gdGhhdCB0aGUgZGF0YSBpcyBlYXNpZXIgdG8gd29yayB3aXRoLiBUaGlzIGlzIGEgc2ltaWxhciBvcGVyYXRpb24gdG8gYSBWTE9PS1VQIGluIGV4Y2VsLiBXZSB1c2UgdGhlIGBmcm9tX2lkYCBhbmQgYHRvX2lkYCBmaWVsZHMgYXMgdGhleSByZXByZXNlbnQgdGhlIGBwYXJlbnRfc3RhdGlvbmAuIA0KDQoNCkZvciBtb3JlIGd1aWRhbmNlIG9uIGBkcGx5cmAgZnVuY3Rpb25zIChsaWtlIGBsZWZ0X2pvaW4oKWApIGNoZWNrIG91dCB0aGlzIFtjaGVhdCBzaGVldF0oaHR0cHM6Ly9ueXUtY2RzYy5naXRodWIuaW8vbGVhcm5pbmdyL2Fzc2V0cy9kYXRhLXRyYW5zZm9ybWF0aW9uLnBkZikNCg0KYGBge3J9DQojIGpvaW4gdGhlIGdlb21ldHJ5IG9mIHRoZSBkZXN0aW5hdGlvbiBzdGF0aW9uDQpnZW9tZXRyeV90b19qb2luIDwtIHNlbGVjdChzdG9wcyxpZCkgIyBkcm9wIHVubmVjZXNzYXJ5IGNvbHMNCmdlb21ldHJ5X3RvX2pvaW4gPC0gcmVuYW1lKGdlb21ldHJ5X3RvX2pvaW4sdG9faWQgPSAiaWQiKSAjIHJlbmFtZSAiaWQiIHRvICJ0b19pZCIgc28gdGhhdCB0aGUgZGF0YSBpcyBqb2luZWQgYXMgZXhwZWN0ZWQNCg0KVFRNIDwtDQogIGxlZnRfam9pbihUVE0sIA0KICAgICAgICAgICAgZ2VvbWV0cnlfdG9fam9pbiwNCiAgICAgICAgICAgIGJ5ID0gInRvX2lkIikNCg0KDQojIGpvaW4gdGhlIHN0YXRpb24gbmFtZSBvZiB0aGUgb3JpZ2luIHN0YXRpb24NCg0KbmFtZXNfdG9fam9pbiA8LSBzZWxlY3Qoc3RvcHMscGFyZW50X3N0YXRpb25fbmFtZSxpZCkgIyBkcm9wIHVubmVjZXNzYXJ5IGNvbHMNCm5hbWVzX3RvX2pvaW4gPC0gc3RfZHJvcF9nZW9tZXRyeShuYW1lc190b19qb2luKSAjIGRyb3AgZ2VvbWV0cnkNCm5hbWVzX3RvX2pvaW4gPC0gcmVuYW1lKG5hbWVzX3RvX2pvaW4sZnJvbV9pZCA9ICJpZCIpICMgcmVuYW1lICJpZCIgdG8gImZyb21faWQiIHNvIHRoYXQgdGhlIGRhdGEgaXMgam9pbmVkIGFzIGV4cGVjdGVkDQoNCg0KVFRNIDwtDQogIGxlZnRfam9pbihUVE0sIA0KICAgICAgICAgICAgbmFtZXNfdG9fam9pbiwNCiAgICAgICAgICAgIGJ5ID0gImZyb21faWQiKQ0KDQpUVE0gPC0gc3RfYXNfc2YoVFRNKQ0KDQpUVE0NCmBgYA0KDQpUaGlzIGZvcm1hdCBpcyBlYXNpZXIgdG8gaW50ZXJwcmV0IGlmIHdlIHRha2UgYSBzaW5nbGUgc3RvcC4gRm9yIGV4YW1wbGUsIGxldHMgZmluZCBhbGwgc3RvcHMgdGhhdCBjYW4gYmUgcmVhY2hlZCBmcm9tIFRoZXJlc2llbnN0cmHDn2Ugd2l0aGluIGEgbWVkaWFuIHRyYXZlbCB0aW1lIG9mIDIwIG1pbnV0ZXMuDQoNCmBgYHtyfQ0KdHNfc3RvcHNfMjAgPC0gZmlsdGVyKFRUTSxwYXJlbnRfc3RhdGlvbl9uYW1lID09ICJUaGVyZXNpZW5zdHJhw59lIiwgdHJhdmVsX3RpbWVfcDUwIDw9IDIwKQ0KDQoNCmdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGEgPSBhcmVhKSsNCiAgZ2VvbV9zZihkYXRhID0gdHNfc3RvcHNfMjAsIGNvbG9yID0gImxpZ2h0Ymx1ZSIpKw0KICBnZW9tX3NmKGRhdGEgPSBmaWx0ZXIoc3RvcHMscGFyZW50X3N0YXRpb25fbmFtZSA9PSAiVGhlcmVzaWVuc3RyYcOfZSIpLCBjb2xvciA9ICJibHVlIikNCmBgYA0KDQpMZXQncyBleHBvcnQgdGhpcyBzbyB0aGF0IHdlIGNhbiB3b3JrIHdpdGggaXQgaW4gUUdJUy4NCg0KYGBge3J9DQpzdF93cml0ZShUVE0sIi4vb3V0cHV0L1RUTS5ncGtnIixhcHBlbmQgPSBGKQ0KYGBgDQpXZSBjYW4gYWNoaWV2ZSB0aGUgc2FtZSByZXN1bHQgYXMgYWJvdmUgYnkgZG9pbmcgdGhlIGZvbGxvd2luZzoNCg0KIVtdKC4vaW1nL3FnaXNfdHRtLnBuZykNCg0KDQpUaGVuLCBieSB1c2luZyB3aGF0IHlvdSd2ZSBsZWFybmVkIGluIHByZXZpb3VzIHR1dG9yaWFscywgeW91IGNhbiBjcmVhdGUgc2VydmljZSBhcmVhczoNCg0KIVtdKC4vaW1nL3NlcnZpY2VfYXJlYV9leGFtcGxlLnBuZykNCg0KDQoNCg0K